package com.pcafs.manage.util;

import Jama.Matrix;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;

/*
 * 算法步骤:
 * 1)将原始数据按列组成n⾏m列矩阵X
 * 2)特征中⼼化。即每⼀维的数据都减去该维的均值，使每⼀维的均值都为0
 * 3)求出协⽅差矩阵
 * 4)求出协⽅差矩阵的特征值及对应的特征向量
 * 5)将特征向量按对应的特征值⼤⼩从上往下按⾏排列成矩阵，取前k⾏组成矩阵p
 * 6)Y=PX 即为降维到k维后的数据
 */
public class PCA {
    private static final double threshold = 0.95;// 特征值阈值

    /**
     * 使每个样本的均值为0
     *
     * @param primary 原始⼆维数组矩阵
     * @return averageArray 中⼼化后的矩阵
     */
    public double[][] changeAverageToZero(double[][] primary) {

        int n = primary.length;
        int m = primary[0].length;
        double[] sum = new double[m];
        double[] average = new double[m];
        double[][] averageArray = new double[n][m];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                sum[i] += primary[j][i];
            }
            average[i] = sum[i] / n;
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                averageArray[j][i] = primary[j][i] - average[i];
            }
        }
        return averageArray;
    }

    /**
     * 计算协⽅差矩阵
     *
     * @param matrix 中⼼化后的矩阵
     * @return result 协⽅差矩阵
     */
    public double[][] getVarianceMatrix(double[][] matrix) {
        int n = matrix.length;// ⾏数
        int m = matrix[0].length;// 列数
        double[][] result = new double[m][m];// 协⽅差矩阵
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < m; j++) {
                double temp = 0;
                for (int k = 0; k < n; k++) {
                    temp += matrix[k][i] * matrix[k][j];
                }
                result[i][j] = temp / (n - 1);
            }
        }
        return result;
    }

    /**
     * 求特征值矩阵
     *
     * @param matrix 协⽅差矩阵
     * @return result 向量的特征值⼆维数组矩阵
     */
    public double[][] getEigenvalueMatrix(double[][] matrix) {
        Matrix A = new Matrix(matrix);
        // 由特征值组成的对⾓矩阵,eig()获取特征值
        A.eig().getD().print(10, 6);
        double[][] result = A.eig().getD().getArray();
        return result;
    }

    /**
     * 标准化矩阵(特征向量矩阵)
     *
     * @param matrix 特征值矩阵
     * @return result 标准化后的⼆维数组矩阵
     */
    public double[][] getEigenVectorMatrix(double[][] matrix) {
        Matrix A = new Matrix(matrix);
        A.eig().getV().print(6, 2);
        double[][] result = A.eig().getV().getArray();
        return result;
    }

    /**
     * 寻找主成分
     *
     * @param primaryArray 原始⼆维数组数组
     * @param eigenvalue    特征值⼆维数组
     * @param eigenVectors  特征向量⼆维数组
     * @return principalMatrix 主成分矩阵
     */
    public Matrix getPrincipalComponent(double[][] primaryArray,
                                        double[][] eigenvalue, double[][] eigenVectors) {
        Matrix A = new Matrix(eigenVectors);// 定义⼀个特征向量矩阵
        double[][] tEigenVectors = A.transpose().getArray();// 特征向量转置
        Map principalMap = new HashMap();// key=主成分特征值，value=该特征值对应的特征向量
        TreeMap eigenMap = new TreeMap(
                Collections.reverseOrder());// key=特征值，value=对应的特征向量；初始化为翻转排序，使map按key值降序排列
        double total = 0;// 存储特征值总和
        int index = 0, n = eigenvalue.length;
        double[] eigenvalueArray = new double[n];// 把特征值矩阵对⾓线上的元素放到数组eigenvalueArray⾥
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) {
                    eigenvalueArray[index] = eigenvalue[i][j];
                }
            }
            index++;
        }
        for (int i = 0; i < tEigenVectors.length; i++) {
            double[] value = new double[tEigenVectors[0].length];
            value = tEigenVectors[i];
            eigenMap.put(eigenvalueArray[i], value);
        }
        // 求特征总和
        for (int i = 0; i < n; i++) {
            total += eigenvalueArray[i];
        }
        // 选出前⼏个主成分
        double temp = 0;
        int principalComponentNum = 0;// 主成分数
        List plist = new ArrayList();// 主成分特征值
        for (Object key1 : eigenMap.keySet()) {
            double key = (double) key1;
            if (temp / total <= threshold) {
                temp += key;
                plist.add(key);
                principalComponentNum++;
            }
        }
        System.out.println("\n" + "当前阈值: " + threshold);
        System.out.println("取得的主成分数: " + principalComponentNum + "\n");
        // 往主成分map⾥输⼊数据
        for (int i = 0; i < plist.size(); i++) {
            if (eigenMap.containsKey(plist.get(i))) {
                principalMap.put(i, eigenMap.get(plist.get(i)));
            }
        }
        // 把map⾥的值存到⼆维数组⾥
        double[][] principalArray = new double[principalMap.size()][];

//        Iterator iterator = principalMap.values().iterator();
//        for (int i = 0; iterator.hasNext(); i++) {
//            principalArray[i] = (double[]) iterator.next();
//        }

        Iterator<Map.Entry<Integer, double[]>> it = principalMap.entrySet()
                .iterator();
        for (int i = 0; it.hasNext(); i++) {
            principalArray[i] = it.next().getValue();
        }


//        Iterator it = principalMap.entrySet().iterator();
//        for (int i = 0; it.hasNext(); i++) {
//
//            principalArray[i] = (double[]) it.next().getValue();
//        }
        Matrix principalMatrix = new Matrix(principalArray);
        return principalMatrix;

    }



    /**
     * 矩阵相乘
     *
     * @param primary 原始⼆维数组
     * @param matrix  主成分矩阵
     * @return result 结果矩阵
     */
    public Matrix getResult(double[][] primary, Matrix matrix) {
        Matrix primaryMatrix = new Matrix(primary);
        Matrix result = primaryMatrix.times(matrix.transpose());
        return result;
    }

    // map 转 二维数组
    public double[][] toEwsz(List<LinkedHashMap> list){
        int x = list.size();
        int y = list.get(0).size();

        double[][] data = new double[x][y];
        for (int i = 0; i < x; i++) {
            Map map = list.get(i);

            Iterator it = map.values().iterator();
            for (int j = 0; it.hasNext(); j++) {
                data[i][j] = Double.parseDouble(it.next().toString()) ;
            }
        }
        return data;
    }


    // 去中心化
    public double[][] ysData(List<LinkedHashMap> list){
        double[][] averageArray = this.changeAverageToZero(this.toEwsz(list));
        return averageArray;
    }

    // 協方差
    public double[][] xfcData(List<LinkedHashMap> list){
        double[][] averageArray = this.changeAverageToZero(this.toEwsz(list));
        double[][] varMatrix = this.getVarianceMatrix(averageArray);
        return varMatrix;
    }
    // 特征值矩阵
    public double[][] tzzjzData(List<LinkedHashMap> list){
        double[][] averageArray = this.changeAverageToZero(this.toEwsz(list));
        double[][] varMatrix = this.getVarianceMatrix(averageArray);
        double[][] eigenvalueMatrix = this.getEigenvalueMatrix(varMatrix);
        return eigenvalueMatrix;
    }
    // 特征向量矩阵
    public double[][] tzzxlData(List<LinkedHashMap> list){
        double[][] averageArray = this.changeAverageToZero(this.toEwsz(list));
        double[][] varMatrix = this.getVarianceMatrix(averageArray);
        double[][] eigenVectorMatrix = this.getEigenVectorMatrix(varMatrix);
        return eigenVectorMatrix;
    }



//    public static void main(String[] args) {
//        PCA pca = new PCA();
//        double[][] primaryArray = {
//                {0.041	,0.863,	0.083	,1.467	,-0.670	,-0.575	,-0.195	,0.156	,-0.033	,-0.096	},
//                {0.090	,1.387,	0.175	,2.057	,0.578	,0.628	,-0.142	,-0.401	,-0.251	,-0.330	},
//                {0.002	,8.044,	0.012	,1.668	,0.341	,0.419	,-0.480	,-0.475	,-0.381	,-0.380	},
//                {0.257	,5.681,	0.420	,4.673	,0.899	,0.987	,0.566	,-0.217	,0.177	,0.031	},
//                {0.587	,5.422,	0.725	,7.579	,2.997	,3.420	,0.770	,-1.684	,-2.198	,-1.998	},
//                {0.066	,1.330,	0.164	,4.996	,0.407	,0.448	,0.119	,-0.200	,0.768	,0.699	},
//                {-0.590	,4.116,	-0.409	,4.965	,0.094	,0.130	,-0.258	,-0.670	,0.240	,0.110	},
//                {0.119	,1.818,	0.160	,4.484	,0.561	,0.650	,0.029	,-0.239	,0.043	,0.097	},
//                {-0.053	,0.257,	-0.045	,3.724	,0.661	,0.889	,0.209	,0.192	,0.224	,0.207	},
//                {0.121	,9.916,	0.352	,4.078	,0.384	,0.542	,0.504	,0.296	,-0.857	,0.197	},
//                {0.111	,2.319,	0.222	,5.231	,2.801	,3.129	,0.120	,-0.086	,-0.337	,-0.452	},
//                {-0.748	,3.428,	-0.690	,-0.149	,-1.807	,-1.793	,0.030	,-0.048	,0.056	,0.005	},
//                {0.015	,3.654,	0.361	,7.268	,3.195	,3.721	,1.147	,1.639	,0.894	,0.616	},
//                {0.986	,4.431,	1.377	,6.286	,2.453	,2.752	,0.629	,-0.584	,-0.539	,1.440	},
//                {0.469	,2.089,	0.171	,10.370	,2.822	,2.996	,-0.667	,-3.735	,-3.660	,-3.960	},
//                {0.351	,4.172,	0.530	,6.584	,2.493	,2.694	,0.216	,-0.372	,0.110	,0.039}
//        };
//        System.out.println("--------------------------------------------");
//        System.out.println("原始数据: ");
//        System.out.println(primaryArray.length + "⾏，" + primaryArray[0].length + "列");
//        for (int i = 0; i < primaryArray.length; i++) {
//            for (int j = 0; j < primaryArray[0].length; j++) {
//                System.out.print(+primaryArray[i][j] + " \t");
//            }
//            System.out.println();
//        }
//        // 均值中⼼化后的矩阵
//        double[][] averageArray = pca.changeAverageToZero(primaryArray);
//        System.out.println("--------------------------------------------");
//        System.out.println("均值0化后的数据: ");
//        System.out.println(averageArray.length + "⾏，" + averageArray[0].length + "列");
//        for (int i = 0; i < averageArray.length; i++) {
//            for (int j = 0; j < averageArray[0].length; j++) {
//                System.out.print((float) averageArray[i][j] + " \t");
//            }
//            System.out.println();
//        }
//        // 协⽅差矩阵
//        double[][] varMatrix = pca.getVarianceMatrix(averageArray);
//        System.out.println("---------------------------------------------");
//        System.out.println("协⽅差矩阵: ");
//        for (int i = 0; i < varMatrix.length; i++) {
//            for (int j = 0; j < varMatrix[0].length; j++) {
//                System.out.print((float) varMatrix[i][j] + "\t");
//            }
//            System.out.println();
//        }
//        // 特征值矩阵
//        System.out.println("--------------------------------------------");
//        System.out.println("特征值矩阵: ");
//        double[][] eigenvalueMatrix = pca.getEigenvalueMatrix(varMatrix);
//        // 特征向量矩阵
//        System.out.println("--------------------------------------------");
//        System.out.println("特征向量矩阵: ");
//        double[][] eigenVectorMatrix = pca.getEigenVectorMatrix(varMatrix);
//        // 主成分矩阵
//        System.out.println("--------------------------------------------");
//        Matrix principalMatrix = pca.getPrincipalComponent(primaryArray, eigenvalueMatrix, eigenVectorMatrix);
//        System.out.println("主成分矩阵: ");
//        principalMatrix.print(6, 2);
//        // 降维后的矩阵
//        System.out.println("--------------------------------------------");
//        System.out.println("降维后的矩阵: ");
//        Matrix resultMatrix = pca.getResult(primaryArray, principalMatrix);
//        resultMatrix.print(10, 2);
//    }
}


